#include <xpp/net/UdpSocket.h>
#include <xpp/net/UdpAddress.h>
#include <xpp/net/Udp4Address.h>
#include <xpp/bas/List.h>
#include <xp/bas/stdio.h>

#ifdef __sun
#include <string.h>
#endif

using namespace xpp;
using namespace xpp::bas;
using namespace xpp::net;

struct client_t
{
	UdpSocket* socket;
	UdpAddress address;
	client_t* next;
};

client_t* client = XP_NULL;

UdpSocket local_socket;
Udp4Address local_addr (XP_TEXT("domain"));
Udp4Address target_addr (XP_TEXT("domain"), XP_TEXT("168.126.63.1"));

void print_address (const xp_char_t* title, const InetAddress& addr)
{
	xp_char_t hbuf0[100], sbuf0[100];
	xp_char_t hbuf1[NI_MAXHOST + 1], sbuf1[NI_MAXSERV + 1];

	addr.getNumericHost (hbuf0, xp_countof(hbuf0));
	addr.getNumericService (sbuf0, xp_countof(sbuf0));
	addr.getHostName (hbuf1, xp_countof(hbuf1));
	addr.getServiceName (sbuf1, xp_countof(sbuf1));

	xp_printf (XP_TEXT("%s\n"), title);
	xp_printf (
		XP_TEXT("\tisLoopBack: %d isAnyLocal: %d\n"),
		addr.isLoopBack(), addr.isAnyLocal());
	xp_printf (
		XP_TEXT("\tNumeric: %s,%s Name: %s,%s\n"), hbuf0, sbuf0, hbuf1, sbuf1);
}

int do_in (const UdpAddress& from, xp_byte_t* buf, xp_size_t size)
{
	client_t* c;

	/*
	for (c = client; c != XP_NULL; c = c->next) {
	}
	*/

	print_address (XP_TEXT("do_in"), from);
	UdpSocket* socket = new UdpSocket ();

	UdpAddress any_addr (from.family());
	if (socket->open (any_addr) == -1) {
		delete socket;
		xp_printf (XP_TEXT("can't open a udp socket - %d\n"), socket->errorCode());
		return -1;
	}

	if (socket->bind () == -1) {
		delete socket;
		xp_printf (XP_TEXT("can't bind\n"));
		return -1;
	}

	if (socket->send (buf, size, target_addr) == -1) {
		delete socket;
		xp_printf (XP_TEXT("can't send\n"));
		return -1;
	}

	client_t* nc = new client_t;
	nc->socket  = socket;
	nc->address = from;
	nc->next = client;
	client = nc;

	return 0;
}

int do_out (client_t* c, xp_byte_t* buf, xp_size_t size)
{
	print_address (XP_TEXT("do_out"), c->address);

	if (local_socket.send (buf, size, c->address) == -1) {
		// TODO:...
		// delete c; remove it from the clienit list...
		return -1;
	}
	return 0;
}

extern "C" { int xp_main (int argc, xp_char_t* argv[]); }
int xp_main (int argc, xp_char_t* argv[])
{
	if (local_socket.open(local_addr) == -1) {
		xp_printf (XP_TEXT("open error - %d\n"), local_socket.errorCode());
		return -1;
	}

	local_socket.setReuseAddr (1);

	if (local_socket.bind () == -1) {
		xp_printf (XP_TEXT("bind error - %d\n"), local_socket.errorCode());
		return -1;
	}

	for (;;) {
		fd_set read_set;
		struct timeval tv;
		client_t* c = client;
		xp_byte_t buf[65535];

		tv.tv_sec = 1;
		tv.tv_usec = 0;

		int highest = 0;

		FD_ZERO (&read_set);
		FD_SET (local_socket.handle(), &read_set);
		if (local_socket.handle() > highest) highest = local_socket.handle();
		while (c != XP_NULL) {
			FD_SET (c->socket->handle(), &read_set);
			if (c->socket->handle() > highest) highest = c->socket->handle();
			c = c->next;
		}

		int n = ::select (highest + 1, &read_set, XP_NULL, XP_NULL, &tv);
		if (n == -1) {
			xp_printf (XP_TEXT("select error"));
			continue;
		}
		if (n == 0) continue;

		if (FD_ISSET(local_socket.handle(), &read_set)) {
			UdpAddress from;
			xp_ssize_t n2 = local_socket.receive (buf, xp_sizeof(buf), &from);
			if (n2 <= 0) {
				xp_printf (XP_TEXT("receive error"));
			}
			else do_in (from, buf, n2);
		}

		for (c = client; c != XP_NULL; c = c->next) {
			if (!FD_ISSET(c->socket->handle(), &read_set)) continue;

			UdpAddress from;
			xp_ssize_t n2 = c->socket->receive (buf, xp_sizeof(buf), &from);
			if (n2 <= 0) {
				xp_printf (XP_TEXT("receive error"));
			}
			else do_out (c, buf, n2);
		}

	}

	local_socket.close ();

	return 0;
}
